package ngat.oss.simulation;

import java.io.*;
import java.util.*;
import java.rmi.*;
import java.rmi.server.*;

import ngat.util.*;
import ngat.phase2.*;
import ngat.astrometry.*;
import ngat.util.logging.*;

/** Phase2 Model Generator. Generates Phase2Models according to supplied control parameters.*/
public class Phase2ModelGenerator extends UnicastRemoteObject implements Phase2ModelProvider {

    /** An optional Phase2Model provider which can be registered.*/    
    Phase2ModelProvider provider;

    /** The Phas2Model.*/
    BasicMemoryPhase2Model phase2Model;

    /** Name of the model.*/
    String modelName;

    /** Date of the model.*/
    long modelDate;

    /** Create a Phase2ModelGenerator.*/
    public Phase2ModelGenerator() throws RemoteException {
	super();	
    }

    /** Setup model generator params from the supplied configuration.
     * @param config A set of configuration properties to define the model.
     * @return Pre-configured Phase2ModelGeneratorParams.
     */
    public Phase2ModelGeneratorParams createModelParams(ConfigurationProperties config) throws Exception {

	Phase2ModelGeneratorParams p = new Phase2ModelGeneratorParams();

	p.modelName = config.getProperty("model-name");
	
	p.root      = config.getProperty("root");

	p.site      = config.getProperty("site-name");

	p.latitude  = Math.toRadians(config.getDoubleValue("site-latitude"));
	p.longitude = Math.toRadians(config.getDoubleValue("site-longitude"));

	p.nProps  = config.getIntValue("number-proposals");

	p.nGroups = config.getIntValue("number-groups");

	p.nObs    = config.getIntValue("number-obs");

	p.time    = (config.getDateValue("date", "yyyy-MM-dd")).getTime();
	p.backage = 24*3600*1000*config.getLongValue("back-age"); // entered as days
	p.foreage = 24*3600*1000*config.getLongValue("fore-age"); // entered as days
	
	p.flexGroupFraction    = config.getDoubleValue("flex-group-fraction");
	p.monitorGroupFraction = config.getDoubleValue("monitor-group-fraction");

	// Mon Group cycle distribution. (3 sets)
	p.monitorGroupPeriodFrac1  = config.getDoubleValue("monitor-group-period-frac1");
	p.monitorGroupPeriodMean1  = config.getLongValue("monitor-group-period-mean1");
	p.monitorGroupPeriodSigma1 = config.getLongValue("monitor-group-period-sigma1");

	p.monitorGroupPeriodFrac2  = config.getDoubleValue("monitor-group-period-frac2");
	p.monitorGroupPeriodMean2  = config.getLongValue("monitor-group-period-mean2");
	p.monitorGroupPeriodSigma2 = config.getLongValue("monitor-group-period-sigma2");

	p.monitorGroupPeriodFrac3  = config.getDoubleValue("monitor-group-period-frac3");
	p.monitorGroupPeriodMean3  = config.getLongValue("monitor-group-period-mean3");
	p.monitorGroupPeriodSigma3 = config.getLongValue("monitor-group-period-sigma3");

	// MinInt Group interval distribution.(3 sets)
	p.intervalGroupIntervalFrac1  = config.getDoubleValue("interval-group-interval-frac1");
	p.intervalGroupIntervalMean1  = config.getLongValue("interval-group-interval-mean1");
	p.intervalGroupIntervalSigma1 = config.getLongValue("interval-group-interval-sigma1");

	p.intervalGroupIntervalFrac2  = config.getDoubleValue("interval-group-interval-frac2");
	p.intervalGroupIntervalMean2  = config.getLongValue("interval-group-interval-mean2");
	p.intervalGroupIntervalSigma2 = config.getLongValue("interval-group-interval-sigma2");

	p.intervalGroupIntervalFrac3  = config.getDoubleValue("interval-group-interval-frac3");
	p.intervalGroupIntervalMean3  = config.getLongValue("interval-group-interval-mean3");
	p.intervalGroupIntervalSigma3 = config.getLongValue("interval-group-interval-sigma3");


	// Condition fractions.
	p.groupPoorSeeingFraction    = config.getDoubleValue("group-poor-fraction");
	p.groupAverageSeeingFraction = config.getDoubleValue("group-average-fraction");
	p.groupPhotomFraction        = config.getDoubleValue("group-photom-fraction");
	p.groupDarkFraction          = config.getDoubleValue("group-dark-fraction");

	// Observation distributions.
	p.exposureMaxCount          = config.getIntValue("exposure-max-count");
	p.observationExposureFrac1  = config.getDoubleValue("exposure-frac1");
	p.observationExposureMean1  = config.getDoubleValue("exposure-mean1");
	p.observationExposureSigma1 = config.getDoubleValue("exposure-sigma1");

	p.observationExposureFrac2  = config.getDoubleValue("exposure-frac2");
	p.observationExposureMean2  = config.getDoubleValue("exposure-mean2");
	p.observationExposureSigma2 = config.getDoubleValue("exposure-sigma2");

	// Accounting.
	p.minBudget = config.getDoubleValue("min-budget");
	p.maxBudget = config.getDoubleValue("max-budget");;

	p.minUsedFraction = config.getDoubleValue("min-used-frac");
	p.maxUsedFraction = config.getDoubleValue("max-used-frac");
	
	// Priority (proposals go 1(high) to 3(low) - can add more as required
	p.proposalPriorityFracP1 = config.getDoubleValue("proposal-priority-frac-p1");
	p.proposalPriorityFracP2 = config.getDoubleValue("proposal-priority-frac-p2");
	p.proposalPriorityFracP3 = config.getDoubleValue("proposal-priority-frac-p3");
	
	// Priority (groups go 0(low) to 5(high) + STD and BGR - can add more as required
	p.groupPriorityFracP1  = config.getDoubleValue("group-priority-frac-p1");
	p.groupPriorityFracP2  = config.getDoubleValue("group-priority-frac-p2");
	p.groupPriorityFracP3  = config.getDoubleValue("group-priority-frac-p3");
	p.groupPriorityFracP4  = config.getDoubleValue("group-priority-frac-p4");
	p.groupPriorityFracP5  = config.getDoubleValue("group-priority-frac-p5");
	p.groupPriorityFracBgr = config.getDoubleValue("group-priority-frac-bgr"); // background
	p.groupPriorityFracStd = config.getDoubleValue("group-priority-frac-std"); // standards

	p.execProbability      = config.getDoubleValue("group-execution-probability"); // p.exec per window for mig/mog

	return p;

    }

    /** Create a Phase2model from supplied model parameters..
     * @param params The Phase2ModelGeneratorParam which define the model.
     */
    public void createPhase2Model(Phase2ModelGeneratorParams params, 
				  ExecutionTimingModel execModel, 
				  TimingConstraintWindowCalculator tcwc) throws Exception {
	
	modelName = params.site+"/"+params.modelName;
	modelDate = params.time;

	// Create the basic model with given root name
	phase2Model = new BasicMemoryPhase2Model(params.root);

	// Clear the Phase2Model.*/
	phase2Model.clear();

	// setup some Gaussian random generators.
	GaussianRandom gmon1 = new GaussianRandom(params.monitorGroupPeriodMean1,    params.monitorGroupPeriodSigma1, params.monitorGroupPeriodMean1);
	GaussianRandom gmon2 = new GaussianRandom(params.monitorGroupPeriodMean2,    params.monitorGroupPeriodSigma2, params.monitorGroupPeriodMean2);
	GaussianRandom gmon3 = new GaussianRandom(params.monitorGroupPeriodMean3,    params.monitorGroupPeriodSigma3, params.monitorGroupPeriodMean3);

	GaussianRandom gint1 = new GaussianRandom(params.intervalGroupIntervalMean1, params.intervalGroupIntervalSigma1, params.intervalGroupIntervalMean1);
	GaussianRandom gint2 = new GaussianRandom(params.intervalGroupIntervalMean2, params.intervalGroupIntervalSigma2, params.intervalGroupIntervalMean2);
	GaussianRandom gint3 = new GaussianRandom(params.intervalGroupIntervalMean3, params.intervalGroupIntervalSigma3, params.intervalGroupIntervalMean3);
	
	GaussianRandom gexp1 = new GaussianRandom(params.observationExposureMean1,   params.observationExposureSigma1, params.observationExposureMean1);
	GaussianRandom gexp2 = new GaussianRandom(params.observationExposureMean2,   params.observationExposureSigma2, params.observationExposureMean2);
	
	int [] gp = new int[5];
	for (int i = 0; i < 5; i++) {
	    gp[i] = 0;
	}
	int gbgr = 0;
	int gstd = 0;

	int gpo = 0;
	int gav = 0;
	int gex = 0;

	int gobs = 0;
	int gh   = 0;

	// Use time at centre of DB time-span (around local midnight).
	long localMidnight = computeMidnight(params.time, params.longitude);
	double clst = JSlalib.getGMST(localMidnight, params.longitude);

	// This range won't work in the SOUTH ...
	double ra1 = clst - 0.5*Math.PI;
	double ra2 = clst + 0.5*Math.PI;

	for (int ip = 0; ip < params.nProps; ip++) {

	    Proposal prop = new Proposal("Prop:"+ip);

	    // set the proposal up for 
	    prop.setPath(params.root+"/Tag/User");
	    prop.setActivationDate(params.time - params.backage);
	    prop.setExpiryDate(params.time + params.foreage);
	    
	    prop.setPriority(createProposalPriority(params)); // random value somehow...
	    System.err.println("Prop:"+prop);

	    // Generate proposal allocation/usage info...
	    double budget = urandom(params.minBudget, params.maxBudget);
	    prop.setAllocatedTime((float)budget );
	    double frac = urandom(params.minUsedFraction, params.maxUsedFraction);
	    prop.setUsedTime((float) (frac*budget));
	    
	    

	    phase2Model.addProposal(prop);
	
	    int nGroups = urandom(1, params.nGroups);
	    for (int ig = 0; ig < nGroups; ig++) {

		Group group = null;

		// Decide on split of groups by timing constraint class

		// #### THIS NEEDS SPLITTING OUT SOAS TO INCLUDE OBOS

		double rnd = Math.random();

		// Generate timing constraint class
		if (rnd < params.flexGroupFraction) {
		    group = new Group("Flex:"+ip+"_"+ig); 
		    
		} else if
		    (rnd < params.monitorGroupFraction) {
		    group = new MonitorGroup("Mon:"+ip+"_"+ig);
		    
		} else {
		    group = new RepeatableGroup("Rep:"+ip+"_"+ig); 
		
		}
		
		
		// Seeing conditions
		double rnd4 = Math.random();
		if (rnd4 < params.groupPoorSeeingFraction) {
		    group.setMinimumSeeing(Group.POOR);
		    gpo++;
		} else if
		    (rnd4 < params.groupAverageSeeingFraction) {
		    group.setMinimumSeeing(Group.AVERAGE);
		    gav++;
		} else {
		    group.setMinimumSeeing(Group.EXCELLENT);
		    gex++;
		    group.setPhotometric(Math.random() < params.groupPhotomFraction);
		}
		// Lunar conditions
		if (Math.random() <  params.groupDarkFraction)
		    group.setMinimumLunar(Group.DARK);
		else
		    group.setMinimumLunar(Group.BRIGHT);
		    
		int pp = createGroupPriority(params);
		group.setPriority(pp);

		switch (pp) {
		case -2:
		    gbgr++;
		    break;
		case -1:
		    gstd++;
		    break;
		default:
		    gp[pp-1]++;
		    break;
		}

	
		// Create a target for ALL obs in group, with appropriate sky distribution.
		ExtraSolarSource src = new ExtraSolarSource("Src:"+ip+"_"+ig);
		double ra = urandom(clst - 0.5*Math.PI, clst + 0.5*Math.PI);
		if (ra < 0)
		    ra += 2.0*Math.PI;
		if (ra >2.0*Math.PI)
		    ra -= 2.0*Math.PI;
		src.setRA(ra);
		double dec =  urandom(params.latitude - 0.25*Math.PI, 0.5*Math.PI);
		src.setDec(dec);
		
		System.err.println("P2G::Using Target: "+src);
	
		int nObs = urandom(1, params.nObs);	
		System.err.println("Group will have: "+nObs+" observations");
		for (int iob = 0; iob < nObs; iob++) {
		    gobs++;

		    Observation obs = new Observation("Obs:"+ip+"_"+ig+"_"+iob);

		    double rnd5 = Math.random();
		    if (rnd5 < params.observationExposureFrac1) {			
			obs.setExposeTime((float)gexp1.random());
		    } else {
			obs.setExposeTime((float)gexp2.random());
		    }
		    obs.setNumRuns(urandom(1,params.exposureMaxCount));

		    Mosaic mosaic = new Mosaic("m");
		    mosaic.setPattern(Mosaic.SINGLE);
		    obs.setMosaic(mosaic);

		    // how do we get the ICM to validate this randomly generated thing ?
		    obs.setInstrumentConfig(new InstrumentConfig("IC:"+ip+"_"+ig+"_"+iob));		    		   
		    obs.setSource(src);
		    
		    System.err.println("P2G::Adding obs: "+obs);
		    group.addObservation(obs);

		} // next obs

		if (group instanceof MonitorGroup) {

		    long gstart = urandom(params.time - params.backage, params.time);
                    long gend   = urandom(params.time, params.time + params.foreage);
                    ((MonitorGroup)group).setStartDate(gstart);
                    ((MonitorGroup)group).setEndDate(gend);
                    group.setExpiryDate(gend);

                    double rnd2 = Math.random();
                    if (rnd2 < params.monitorGroupPeriodFrac1) {
                        ((MonitorGroup)group).setPeriod((long)gmon1.random());
                    } else if
                        (rnd2 < params.monitorGroupPeriodFrac1 + params.monitorGroupPeriodFrac2) {
                        ((MonitorGroup)group).setPeriod((long)gmon2.random());
                    } else {
                        ((MonitorGroup)group).setPeriod((long)gmon3.random());
                    }
                    ((MonitorGroup)group).setFloatFraction((float)urandom(0.5, 1.0));

                    gh += generateMonitorGroupHistory(group, gstart, params.time, params.execProbability, execModel, tcwc);

		} else if
		    (group instanceof RepeatableGroup) {
		    
		    long gstart = urandom(params.time - params.backage, params.time);
                    long gend   = urandom(params.time, params.time + params.foreage);

                    ((RepeatableGroup)group).setStartDate(gstart);
                    ((RepeatableGroup)group).setEndDate(gend);
                    group.setExpiryDate(gend);
                    double rnd3 = Math.random();
                    if (rnd3 < params.intervalGroupIntervalFrac1) {
                        ((RepeatableGroup)group).setMinimumInterval((long)gint1.random());
                    } else if
                        (rnd3 < params.intervalGroupIntervalFrac1 + params.intervalGroupIntervalFrac2) {
                        ((RepeatableGroup)group).setMinimumInterval((long)gint2.random());
                    } else {
                        ((RepeatableGroup)group).setMinimumInterval((long)gint3.random());
                    }
                    ((RepeatableGroup)group).setMaximumRepeats(urandom(1,20));

                    long hdate = urandom(gstart, params.time);

                    gh += generateRepeatableGroupHistory(group, gstart, params.time, params.execProbability, execModel);

		} else {
		    
		    long gstart = urandom(params.time - params.backage, params.time);
                    long gend   = urandom(params.time, params.time + params.foreage);
                    group.setStartingDate(gstart);
                    group.setExpiryDate(gend);
		
		}

		prop.addGroup(group);   
		System.err.println("Group: "+group);

	    } // next group

	} // next prop.

	for (int i = 0; i < 5; i++) {
	    System.err.println("NG["+i+"] = "+gp[i]);
	}
	System.err.println("NG[BGR] = "+gbgr);
	System.err.println("NG[STD] = "+gstd);
	System.err.println("NG[SEX] = "+gex);
	System.err.println("NG[SAV] = "+gav);
	System.err.println("NG[SPO] = "+gpo);

	System.err.println("NOBS    = "+gobs);
	System.err.println("NGH     = "+gh);
    }

    /** Generate faked execution history for a group.*/
    private int generateMonitorGroupHistory(Group group, 
					     long t1, 
					     long t2, 
					     double execProbability, 
					     ExecutionTimingModel execModel,
					     TimingConstraintWindowCalculator tcwc) {
	
	long dt = 15*60*1000L;

	int nx = 0;
	
	
	// first we need a temporary exec history
	ExecutionStatistics hist = new ExecutionStatistics(0L, 0);
	EnvironmentSnapshot env = new EnvironmentSnapshot();
	env.seeing = Group.EXCELLENT;
	env.photom = true;
	System.err.println("P2G::Generate history for:[MonGroup] "+group.getName());
	System.err.println("P2G::USe exec probability: "+execProbability);

	List windows = tcwc.listFeasibleWindows(group, hist, t1, t2);
	System.err.println("P2G::Group has "+windows.size()+" possible windows");
	
	Iterator it = windows.iterator();
	while (it.hasNext()) {
	    
	    TimeWindow w = (TimeWindow)it.next();
	    
	    // Scan the enabled period and maybe do the group, then dont try again this window
	    boolean testing = true;
	    long t = w.start;	  
	    w.end = Math.min(w.end, t2); // make sure we dont sneak into future...
	    //System.err.println("P2G::Testing inside pruned window: "+w);
	    while (t < w.end && testing) {
		if (execModel.canDo(group, t, env, hist) ) {
		    if (Math.random() < execProbability) {
			group.updateHistory(t, true, 500, "OK_DOK", "All is well");
			nx++;
			System.err.println("P2G::Simulated Execution for "+nx+" time at "+ScheduleSimulator.sdf.format(new Date(t)));
			hist = new ExecutionStatistics(t, nx);
		    }
		    testing = false; // done now, may have executed or not...
		}
		t += dt;
	    }
	    
	}
	double acf = 100.0*(double)nx/(double)windows.size();
	System.err.println("P2G:: Group has "+nx+" total executions out of potential "+windows.size()+" ACF="+acf) ;
	return nx;
    }

    /** Generate faked execution history for a group.*/
    private int generateRepeatableGroupHistory(Group group, long t1, long t2, double execProbability, ExecutionTimingModel execModel) {
	long dt = 15*60*1000L;

	ExecutionStatistics hist = new ExecutionStatistics(0L, 0);
	EnvironmentSnapshot env = new EnvironmentSnapshot();
	env.seeing = Group.EXCELLENT;
	env.photom = true;
	System.err.println("P2G::Generate history for:[RepGroup] "+group.getName());
	System.err.println("P2G::USe exec probability: "+execProbability);
	long mint = ((RepeatableGroup)group).getMinimumInterval();
	int maxc  =  ((RepeatableGroup)group).getMaximumRepeats();

	// potential windows (approximate)
	int npw = Math.min((int)(((double)t2 - t1)/(double)mint), maxc);
	
	boolean testing = true;
	long ts = t1;	
	int nx = 0;
	while ( (ts < t2) && (nx < maxc)) {
	    // Virtual window runs from [t, t+mint]
	    long te = ts + mint;
	    TimeWindow w = new TimeWindow(ts, te);
	    //System.err.println("P2G::Testing inside pruned window: "+w);
	    long t = w.start;	
	    w.end = Math.min(w.end, t2);
	    while (t < w.end && testing) {
		// Maybe do group in window (allow mint as size)
		if (execModel.isFeasible(group, t)) {
		    //System.err.println("P2G::Group is Feasible at "+ScheduleSimulator.sdf.format(new Date(t))+"...");
		    if (Math.random() < execProbability) {
			group.updateHistory(t, true, 500, "OK_DOK", "All is well");
			nx++;
			System.err.println("P2G::Simulated Execution for "+nx+" time at "+ScheduleSimulator.sdf.format(new Date(t)));
			hist = new ExecutionStatistics(t, nx);
		    }  
		    testing = false; // done now, may have executed or not...
		}
		t += dt;
	    }
	    ts += mint;
	}
	double acf = 100.0*(double)nx/(double)npw;
	System.err.println("P2G:: Group has "+nx+" total executions out of potential "+npw+" ACF="+acf);
	return nx;
    }
    

    /** Dump a phase2model to a file.*/
    public void dumpPhase2Model(File file) throws Exception {

	ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
	oos.flush();
	oos.writeObject(phase2Model.getRootName());
	oos.writeObject(modelName);
	oos.writeLong(modelDate);
	oos.flush();
	Iterator ip = phase2Model.listProposals();
	while (ip.hasNext()) {
	    Proposal proposal = (Proposal)ip.next();
	    oos.writeObject(proposal);
	    oos.flush();
	    System.err.println("Wrote "+proposal.getName()+" and flushed OOS");
	}
	if (oos != null) {
            try {
                oos.close();
            } catch (Exception w) {
                System.err.println("Warning - Failed to close: "+oos);
            }
        }
    }

    /** Load a model from a file.*/
    public void loadPhase2Model(File file) throws Exception {

	ObjectInputStream oin = new ObjectInputStream(new FileInputStream(file));
	String rootName = (String)oin.readObject();
	modelName = (String)oin.readObject();
	modelDate = oin.readLong();

	// Create the model
	phase2Model = new BasicMemoryPhase2Model(rootName);
	phase2Model.clear();

	Proposal proposal = null;
	do {	   
	    try {
		proposal = (Proposal)oin.readObject();
		phase2Model.addProposal(proposal);
	    } catch (Exception e) {
		proposal = null;
	    }
	} while (proposal != null);
	if (oin != null) {
	    try {
		oin.close();	
	    } catch (Exception w) {
		System.err.println("Warning - Failed to close: "+oin);
	    }
	}

    }

    /** Test - create Phase2ModelGenerator and configure.*/
    public static void main(String args[]) {
	
	try {
	    
	    final long TIMESTEP = 10*60*1000L;
	    
	    Logger logger = LogManager.getLogger("SIM");
	    ConsoleLogHandler handler = new ConsoleLogHandler(new SimulationLogFormatter());
	    handler.setLogLevel(3);
	    logger.setLogLevel(3);
	    logger.addHandler(handler);


	    ConfigurationProperties cfg = CommandTokenizer.use("--").parse(args);

	    logger.setLogLevel(cfg.getIntValue("log", 3));
			       
	    File modelFile = new File(cfg.getProperty("model"));
	    ConfigurationProperties model = new ConfigurationProperties();
	    model.load(new FileInputStream(modelFile));

	    Site site = new Site(cfg.getProperty("name"),
				 Math.toRadians(cfg.getDoubleValue("lat")), 
				 Math.toRadians(cfg.getDoubleValue("long")));

	    long eot = (ScheduleSimulator.sdf.parse(cfg.getProperty("eot"))).getTime();

	    BasicExecutionTimingModel exec = new BasicExecutionTimingModel(site, null);

	    File bxf = new File(cfg.getProperty("exec")); // exec model properties
	    PropertiesConfigurator.use(bxf).configure(exec);
	    exec.setExternalTimeConstraint(eot, "TEST_END");
	    BasicTimingConstraintWindowCalculator tcwc = new BasicTimingConstraintWindowCalculator(exec,  TIMESTEP);
	    
	    Phase2ModelGenerator gen = new Phase2ModelGenerator();
	    
	    Phase2ModelGeneratorParams params = gen.createModelParams(model);

	    gen.createPhase2Model(params, exec, tcwc);

	    Naming.rebind("rmi://localhost/"+params.root+"_Phase2ModelProvider", gen);
	    
	    System.err.println("PhaseModelGenerator provider bound to registry");

	    // busy wait to keep alive
            while (true) {
                try{Thread.sleep(60000L);} catch (Exception e) {}
            }

	} catch (Exception e) {
	    e.printStackTrace();
	}
	
    }
  
    /** Returns a reference to a generated Phase2Model.*/
    public Phase2Model getPhase2Model() throws RemoteException {
	return phase2Model;
    }

    /** Returns the Name/ID of the provided model.*/
    public String getModelName() throws RemoteException {
	return modelName;
    }

    /** Returns the date for which the provided model is valid.*/
    public long getModelDate() throws RemoteException{
	return modelDate;
    }


    /** Create a proposal priority value based on distribution.*/
    private int createProposalPriority(Phase2ModelGeneratorParams params) {

	double rnd = Math.random();
	if (rnd < params.proposalPriorityFracP1)
	    return 1;
	else if
	    (rnd < params.proposalPriorityFracP1+
	     params.proposalPriorityFracP2)
	    return 2;
	else 
	    return 3;
	
    }

    /** Create a group priority value based on distribution.*/
    private int createGroupPriority(Phase2ModelGeneratorParams params) {
     
	double rnd = Math.random();
	if (rnd < params.groupPriorityFracP1)
	    return 1;
	else if
	    (rnd < params.groupPriorityFracP1+
	     params.groupPriorityFracP2)
	    return 2;
	else if
	    (rnd < params.groupPriorityFracP1+
	     params.groupPriorityFracP2+
	     params.groupPriorityFracP3)
	    return 3;
	else if
	    (rnd < params.groupPriorityFracP1+
	     params.groupPriorityFracP2+
	     params.groupPriorityFracP3+
	     params.groupPriorityFracP4)
	    return 4;
	else if
	    (rnd < params.groupPriorityFracP1+
	     params.groupPriorityFracP2+
	     params.groupPriorityFracP3+
	     params.groupPriorityFracP4+
	     params.groupPriorityFracP5)
	    return 5;
	else if
	    (rnd < params.groupPriorityFracP1+
	     params.groupPriorityFracP2+
	     params.groupPriorityFracP3+
	     params.groupPriorityFracP4+
	     params.groupPriorityFracP5+
	     params.groupPriorityFracBgr)
	    return -2;
	return -1;

    }

    /** Compute midnight localtime around supplied date for given E longitude.*/
    private long computeMidnight(long atime, double elong) {

	Calendar cal = Calendar.getInstance();
	cal.setTimeZone(ScheduleSimulator.UTC);
	cal.setTime(new Date(atime));

	long time = cal.getTime().getTime();

	int hh = cal.get(Calendar.HOUR_OF_DAY);

	hh = hh + (int)Math.floor(Math.toDegrees(elong)/15.0);

	if (hh < 0)
	    hh += 24;

	if (hh > 24)
	    hh -= 24;

	//System.err.println("Date: "+sdf.format(d)+" at Longitude: "+l+(l < 0.0 ? "W" : "E")+" LH="+hh);

	// compute last valid local noon

	long tnoon = 0L;
	if (hh <= 12)
	    tnoon = time - 3600000L*(12+hh);
	else
	    tnoon = time - 3600000L*(hh-12);

	//System.err.println("Last noon was at: "+sdf.format(new Date(tnoon)));

	long tmid = tnoon + 3600000L*12;

	//System.err.println("Lastish midnight: "+sdf.format(new Date(tmid)));

	return tmid;
    }

    /** Return a double random number uniformly distributed in [a,b].*/
    private double urandom(double a, double b) {
	return a + Math.random()*(b-a);
    }

    /** Return a long random number uniformly distributed in [a,b].*/
    private long urandom(long a, long b) {
	return (long)urandom((double)a, (double)b);
    }

    /** Return an int random number uniformly distributed in [a,b].*/
    private int urandom(int a, int b) {
	return (int)urandom((double)a, (double)b);
    }
}
